package drds.server.sqlparser.visitor;

import static drds.server.sqlparser.visitor.EvalVisitor.EVAL_ERROR;
import static drds.server.sqlparser.visitor.EvalVisitor.EVAL_EXPR;
import static drds.server.sqlparser.visitor.EvalVisitor.EVAL_VALUE;
import static drds.server.sqlparser.visitor.EvalVisitor.EVAL_VALUE_NULL;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

import drds.server.sqlparser.SQLUtils;
import drds.server.sqlparser.sqlobject.SqlObject;
import drds.server.sqlparser.sqlobject.expression.Between;
import drds.server.sqlparser.sqlobject.expression.Expression;
import drds.server.sqlparser.sqlobject.expression.InList;
import drds.server.sqlparser.sqlobject.expression.Variable;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryExpression;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryOperator;
import drds.server.sqlparser.sqlobject.expression.data_type.Null;
import drds.server.sqlparser.sqlobject.expression.data_type.Text;
import drds.server.sqlparser.sqlobject.expression.data_type.number.Numeric;
import drds.server.sqlparser.sqlobject.expression.name.Identifier;
import drds.server.sqlparser.sqlobject.expression.unary.Unary;
import drds.server.sqlparser.visitor.VisitorUtils.WallConditionContext;

/**
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class EvalVisitorUtils {

	public static Object evalExpr(String expr) {
		Expression sqlExpr = SQLUtils.toSQLExpr(expr);
		return eval(sqlExpr);
	}

	public static Object evalExpr(String expr, List<Object> parameters) {
		Expression sqlExpr = SQLUtils.toSQLExpr(expr);
		return eval(sqlExpr, parameters);
	}

	public static Object eval(SqlObject sqlObject) {
		Object value = eval(sqlObject);

		if (value == EVAL_VALUE_NULL) {
			value = null;
		}

		return value;
	}

	public static Object getValue(SqlObject sqlObject) {
		if (sqlObject instanceof Numeric) {
			return ((Numeric) sqlObject).getNumber();
		}

		return sqlObject.getAttributeMap().get(EVAL_VALUE);
	}

	public static Object eval(SqlObject sqlObject, List<Object> parameters) {
		return eval(sqlObject, parameters, true);
	}

	public static Object eval(SqlObject sqlObject, List<Object> parameters, boolean throwError) {
		EvalVisitor visitor = createEvalVisitor();
		sqlObject.accept(visitor);

		Object value = getValue(sqlObject);
		if (value == null) {
			if (throwError && !sqlObject.getAttributeMap().containsKey(EVAL_VALUE)) {
				throw new RuntimeException("eval error : " + SQLUtils.toSQLString(sqlObject));
			}
		}

		return value;
	}

	public static EvalVisitor createEvalVisitor() {
		return new EvalVisitorImpl();
	}

	public static boolean visit(EvalVisitor visitor, Text x) {
		x.putAttribute(EVAL_VALUE, x.getText());
		return true;
	}

	public static Expression unwrap(Expression expr) {
		if (expr == null) {
			return null;
		}

		return expr;
	}

	public static boolean visit(EvalVisitor visitor, Between x) {
		Expression testExpr = unwrap(x.get$());
		testExpr.accept(visitor);

		if (!testExpr.getAttributeMap().containsKey(EVAL_VALUE)) {
			return false;
		}

		Object value = testExpr.getAttribute(EVAL_VALUE);

		Expression beginExpr = unwrap(x.getBegin());
		beginExpr.accept(visitor);
		if (!beginExpr.getAttributeMap().containsKey(EVAL_VALUE)) {
			return false;
		}

		Object begin = beginExpr.getAttribute(EVAL_VALUE);

		if (lt(value, begin)) {
			x.getAttributeMap().put(EVAL_VALUE, x.isNot() ? true : false);
			return false;
		}

		Expression endExpr = unwrap(x.getEnd());
		endExpr.accept(visitor);
		if (!endExpr.getAttributeMap().containsKey(EVAL_VALUE)) {
			return false;
		}

		Object end = endExpr.getAttribute(EVAL_VALUE);

		if (gt(value, end)) {
			x.getAttributeMap().put(EVAL_VALUE, x.isNot() ? true : false);
			return false;
		}

		x.getAttributeMap().put(EVAL_VALUE, x.isNot() ? false : true);
		return false;
	}

	public static boolean visit(EvalVisitor visitor, Null x) {
		x.getAttributeMap().put(EVAL_VALUE, EVAL_VALUE_NULL);
		return false;
	}

	public static boolean visit(EvalVisitor visitor, InList x) {
		Expression valueExpr = x.getExpression();
		valueExpr.accept(visitor);
		if (!valueExpr.getAttributeMap().containsKey(EVAL_VALUE)) {
			return false;
		}
		Object value = valueExpr.getAttribute(EVAL_VALUE);

		for (Expression item : x.getItemList()) {
			item.accept(visitor);
			if (!item.getAttributeMap().containsKey(EVAL_VALUE)) {
				return false;
			}
			Object itemValue = item.getAttribute(EVAL_VALUE);
			if (eq(value, itemValue)) {
				x.getAttributeMap().put(EVAL_VALUE, x.isNot() ? false : true);
				return false;
			}
		}

		x.getAttributeMap().put(EVAL_VALUE, x.isNot() ? true : false);
		return false;
	}

	public static boolean visit(EvalVisitor visitor, Unary x) {
		final WallConditionContext wallConditionContext = VisitorUtils.getWallConditionContext();
		if (wallConditionContext != null) {
			wallConditionContext.setBitwise(true);
		}

		x.getExpression().accept(visitor);

		Object val = x.getExpression().getAttribute(EVAL_VALUE);
		if (val == EVAL_ERROR) {
			x.putAttribute(EVAL_VALUE, EVAL_ERROR);
			return false;
		}

		if (val == null) {
			x.putAttribute(EVAL_VALUE, EVAL_VALUE_NULL);
			return false;
		}

		switch (x.getUnaryOperator()) {

		case Plus:
			x.putAttribute(EVAL_VALUE, val);
			break;
		case SUB:
			x.putAttribute(EVAL_VALUE, multi(val, -1));
			break;

		default:
			break;
		}

		return false;
	}

	public static boolean visit(EvalVisitor visitor, BinaryExpression x) {
		Expression left = unwrap(x.getLeft());
		Expression right = unwrap(x.getRight());

		left.accept(visitor);
		right.accept(visitor);

		final WallConditionContext wallConditionContext = VisitorUtils.getWallConditionContext();
		if (x.getBinaryOperator() == BinaryOperator.Or) {
			if (wallConditionContext != null) {
				if (left.getAttribute(EVAL_VALUE) == Boolean.TRUE || right.getAttribute(EVAL_VALUE) == Boolean.TRUE) {
					wallConditionContext.setPartAlwayTrue(true);
				}
			}
		} else if (x.getBinaryOperator() == BinaryOperator.And) {
			if (wallConditionContext != null) {
				if (left.getAttribute(EVAL_VALUE) == Boolean.FALSE || right.getAttribute(EVAL_VALUE) == Boolean.FALSE) {
					wallConditionContext.setPartAlwayFalse(true);
				}
			}
		}

		Object leftValue = left.getAttribute(EVAL_VALUE);
		Object rightValue = right.getAttributeMap().get(EVAL_VALUE);

		if (x.getBinaryOperator() == BinaryOperator.Like) {
			if (isAlwayTrueLikePattern(x.getRight())) {
				x.putAttribute(VisitorUtils.HAS_TRUE_LIKE, Boolean.TRUE);
				x.putAttribute(EVAL_VALUE, Boolean.TRUE);
				return false;
			}
		}

		if (x.getBinaryOperator() == BinaryOperator.NotLike) {
			if (isAlwayTrueLikePattern(x.getRight())) {
				x.putAttribute(EVAL_VALUE, Boolean.FALSE);
				return false;
			}
		}

		boolean leftHasValue = left.getAttributeMap().containsKey(EVAL_VALUE);
		boolean rightHasValue = right.getAttributeMap().containsKey(EVAL_VALUE);

		if ((!leftHasValue) && !rightHasValue) {
			Expression leftEvalExpr = (Expression) left.getAttribute(EVAL_EXPR);
			Expression rightEvalExpr = (Expression) right.getAttribute(EVAL_EXPR);

			if (leftEvalExpr != null && leftEvalExpr.equals(rightEvalExpr)) {
				switch (x.getBinaryOperator()) {
				case Like:
				case Equal:
				case GreaterThanOrEqual:
				case LessThanOrEqual:
					x.putAttribute(EVAL_VALUE, Boolean.TRUE);
					return false;
				case NotEqual:
				case NotLike:
				case GreaterThan:
				case LessThan:
					x.putAttribute(EVAL_VALUE, Boolean.FALSE);
					return false;
				default:
					break;
				}
			}
		}

		if (!leftHasValue) {
			return false;
		}

		if (!rightHasValue) {
			return false;
		}

		if (wallConditionContext != null) {
			wallConditionContext.setConstArithmetic(true);
		}

		leftValue = processValue(leftValue);
		rightValue = processValue(rightValue);

		if (leftValue == null || rightValue == null) {
			return false;
		}

		Object value = null;
		switch (x.getBinaryOperator()) {
		case Add:
			value = add(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case Subtract:
			value = sub(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case GreaterThan:
			value = gt(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case GreaterThanOrEqual:
			value = gteq(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case LessThan:
			value = lt(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case LessThanOrEqual:
			value = lteq(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case Is:
			if (rightValue == EVAL_VALUE_NULL) {
				if (leftValue != null) {
					value = (leftValue == EVAL_VALUE_NULL);
					x.putAttribute(EVAL_VALUE, value);
					break;
				}
			}
			break;
		case Equal:
			value = eq(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case NotEqual:
			value = !eq(leftValue, rightValue);
			x.putAttribute(EVAL_VALUE, value);
			break;
		case IsNot:
			if (leftValue == EVAL_VALUE_NULL) {
				x.putAttribute(EVAL_VALUE, false);
			} else if (leftValue != null) {
				x.putAttribute(EVAL_VALUE, true);
			}
			break;

		case Like: {
			String pattern = castToString(rightValue);
			String input = castToString(left.getAttributeMap().get(EVAL_VALUE));
			boolean matchResult = like(input, pattern);
			x.putAttribute(EVAL_VALUE, matchResult);
			break;
		}
		case NotLike: {
			String pattern = castToString(rightValue);
			String input = castToString(left.getAttributeMap().get(EVAL_VALUE));
			boolean matchResult = !like(input, pattern);
			x.putAttribute(EVAL_VALUE, matchResult);
			break;
		}
		case And: {
			boolean first = eq(leftValue, true);
			boolean second = eq(rightValue, true);
			x.putAttribute(EVAL_VALUE, first && second);
			break;
		}
		case Or: {
			boolean first = eq(leftValue, true);
			boolean second = eq(rightValue, true);
			x.putAttribute(EVAL_VALUE, first || second);
			break;
		}
		default:
			break;
		}

		return false;
	}

	@SuppressWarnings("rawtypes")
	private static Object processValue(Object value) {
		if (value instanceof List) {
			List list = (List) value;
			if (list.size() == 1) {
				return processValue(list.get(0));
			}
		} else if (value instanceof Date) {
			return ((Date) value).getTime();
		}
		return value;
	}

	private static boolean isAlwayTrueLikePattern(Expression x) {
		if (x instanceof Text) {
			String text = ((Text) x).getText();

			if (text.length() > 0) {
				for (char ch : text.toCharArray()) {
					if (ch != '%') {
						return false;
					}
				}
				return true;
			}
		}
		return false;
	}

	public static boolean visit(EvalVisitor visitor, Numeric x) {
		x.getAttributeMap().put(EVAL_VALUE, x.getNumber());
		return false;
	}

	public static boolean visit(EvalVisitor visitor, Variable x) {
		if (!"?".equals(x.getName())) {
			return false;
		}

		return false;
	}

	public static Boolean castToBoolean(Object val) {
		if (val == null) {
			return null;
		}

		if (val == EVAL_VALUE_NULL) {
			return null;
		}

		if (val instanceof Boolean) {
			return (Boolean) val;
		}

		if (val instanceof Number) {
			return ((Number) val).intValue() > 0;
		}

		if (val instanceof String) {
			if ("1".equals(val) || "true".equalsIgnoreCase((String) val)) {
				return true;
			}

			return false;
		}

		throw new IllegalArgumentException(val.getClass() + " not supported.");
	}

	public static String castToString(Object val) {
		Object value = val;

		if (value == null) {
			return null;
		}

		return value.toString();
	}

	public static Byte castToByte(Object val) {
		if (val == null) {
			return null;
		}

		if (val instanceof Byte) {
			return (Byte) val;
		}

		if (val instanceof String) {
			return Byte.parseByte((String) val);
		}

		return ((Number) val).byteValue();
	}

	public static Short castToShort(Object val) {
		if (val == null || val == EVAL_VALUE_NULL) {
			return null;
		}

		if (val instanceof Short) {
			return (Short) val;
		}

		if (val instanceof String) {
			return Short.parseShort((String) val);
		}

		return ((Number) val).shortValue();
	}

	@SuppressWarnings("rawtypes")
	public static Integer castToInteger(Object val) {
		if (val == null) {
			return null;
		}

		if (val instanceof Integer) {
			return (Integer) val;
		}

		if (val instanceof String) {
			return Integer.parseInt((String) val);
		}

		if (val instanceof List) {
			List list = (List) val;
			if (list.size() == 1) {
				return castToInteger(list.get(0));
			}
		}

		if (val instanceof Boolean) {
			if (((Boolean) val).booleanValue()) {
				return 1;
			} else {
				return 0;
			}
		}

		if (val instanceof Number) {
			return ((Number) val).intValue();
		}

		throw new RuntimeException("cast error");
	}

	@SuppressWarnings("rawtypes")
	public static Long castToLong(Object val) {
		if (val == null) {
			return null;
		}

		if (val instanceof Long) {
			return (Long) val;
		}

		if (val instanceof String) {
			return Long.parseLong((String) val);
		}

		if (val instanceof List) {
			List list = (List) val;
			if (list.size() == 1) {
				return castToLong(list.get(0));
			}
		}

		if (val instanceof Boolean) {
			if (((Boolean) val).booleanValue()) {
				return 1l;
			} else {
				return 0l;
			}
		}

		return ((Number) val).longValue();
	}

	public static Float castToFloat(Object val) {
		if (val == null || val == EVAL_VALUE_NULL) {
			return null;
		}

		if (val instanceof Float) {
			return (Float) val;
		}

		return ((Number) val).floatValue();
	}

	public static Double castToDouble(Object val) {
		if (val == null || val == EVAL_VALUE_NULL) {
			return null;
		}

		if (val instanceof Double) {
			return (Double) val;
		}

		return ((Number) val).doubleValue();
	}

	public static BigInteger castToBigInteger(Object val) {
		if (val == null) {
			return null;
		}

		if (val instanceof BigInteger) {
			return (BigInteger) val;
		}

		if (val instanceof String) {
			return new BigInteger((String) val);
		}

		return BigInteger.valueOf(((Number) val).longValue());
	}

	public static Number castToNumber(String val) {
		if (val == null) {
			return null;
		}

		try {
			return Byte.parseByte(val);
		} catch (NumberFormatException e) {
		}

		try {
			return Short.parseShort(val);
		} catch (NumberFormatException e) {
		}

		try {
			return Integer.parseInt(val);
		} catch (NumberFormatException e) {
		}

		try {
			return Long.parseLong(val);
		} catch (NumberFormatException e) {
		}

		try {
			return Float.parseFloat(val);
		} catch (NumberFormatException e) {
		}

		try {
			return Double.parseDouble(val);
		} catch (NumberFormatException e) {
		}

		try {
			return new BigInteger(val);
		} catch (NumberFormatException e) {
		}

		try {
			return new BigDecimal(val);
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	public static Date castToDate(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Date) {
			return (Date) value;
		}

		if (value instanceof Number) {
			return new Date(((Number) value).longValue());
		}

		if (value instanceof String) {
			return castToDate((String) value);
		}

		throw new RuntimeException("can cast to date");
	}

	public static final String yyyy_MM_dd = "yyyy-MM-dd";
	public static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";
	public static final SimpleDateFormat yyyy_MM_dd$DateFormat = new SimpleDateFormat(yyyy_MM_dd);
	public static final SimpleDateFormat yyyy_MM_dd_HH_mm_ss$DateFormat = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);

	public static Date castToDate(String date) {
		if (date == null || date.length() == 0) {
			return null;
		}

		if (date.length() == "yyyy-MM-dd".length()) {
			try {
				return yyyy_MM_dd$DateFormat.parse(date);
			} catch (ParseException e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
		} else {
			try {
				return yyyy_MM_dd_HH_mm_ss$DateFormat.parse(date);
			} catch (ParseException e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
		}

	}

	public static BigDecimal castToDecimal(Object val) {
		if (val == null) {
			return null;
		}

		if (val instanceof BigDecimal) {
			return (BigDecimal) val;
		}

		if (val instanceof String) {
			return new BigDecimal((String) val);
		}

		if (val instanceof Float) {
			return new BigDecimal((Float) val);
		}

		if (val instanceof Double) {
			return new BigDecimal((Double) val);
		}

		return BigDecimal.valueOf(((Number) val).longValue());
	}

	public static Object rightShift(Object a, Object b) {
		if (a == null || b == null) {
			return null;
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a).longValue() >> castToLong(b).longValue();
		}

		return castToInteger(a).intValue() >> castToInteger(b).intValue();
	}

	public static Object bitAnd(Object a, Object b) {
		if (a == null || b == null) {
			return null;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return null;
		}

		if (a instanceof String) {
			a = castToNumber((String) a);
		}

		if (b instanceof String) {
			b = castToNumber((String) b);
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a).longValue() & castToLong(b).longValue();
		}

		return castToInteger(a).intValue() & castToInteger(b).intValue();
	}

	public static Object bitOr(Object a, Object b) {
		if (a == null || b == null) {
			return null;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return null;
		}

		if (a instanceof String) {
			a = castToNumber((String) a);
		}

		if (b instanceof String) {
			b = castToNumber((String) b);
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a).longValue() | castToLong(b).longValue();
		}

		return castToInteger(a).intValue() | castToInteger(b).intValue();
	}

	public static Object div(Object a, Object b) {
		if (a == null || b == null) {
			return null;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return null;
		}

		if (a instanceof String) {
			a = castToNumber((String) a);
		}

		if (b instanceof String) {
			b = castToNumber((String) b);
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			BigDecimal decimalA = castToDecimal(a);
			BigDecimal decimalB = castToDecimal(b);
			if (decimalB.scale() < decimalA.scale()) {
				decimalB = decimalB.setScale(decimalA.scale());
			}
			try {
				return decimalA.divide(decimalB);
			} catch (ArithmeticException ex) {
				return decimalA.divide(decimalB, BigDecimal.ROUND_HALF_UP);
			}
		}

		if (a instanceof Double || b instanceof Double) {
			Double doubleA = castToDouble(a);
			Double doubleB = castToDouble(b);
			if (doubleA == null || doubleB == null) {
				return null;
			}
			return doubleA / doubleB;
		}

		if (a instanceof Float || b instanceof Float) {
			Float floatA = castToFloat(a);
			Float floatB = castToFloat(b);
			if (floatA == null || floatB == null) {
				return null;
			}
			return floatA / floatB;
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).divide(castToBigInteger(b));
		}

		if (a instanceof Long || b instanceof Long) {
			Long longA = castToLong(a);
			Long longB = castToLong(b);
			if (longB == 0) {
				if (longA > 0) {
					return Double.POSITIVE_INFINITY;
				} else if (longA < 0) {
					return Double.NEGATIVE_INFINITY;
				} else {
					return Double.NaN;
				}
			}
			return longA / longB;
		}

		if (a instanceof Integer || b instanceof Integer) {
			Integer intA = castToInteger(a);
			Integer intB = castToInteger(b);
			if (intB == 0) {
				if (intA > 0) {
					return Double.POSITIVE_INFINITY;
				} else if (intA < 0) {
					return Double.NEGATIVE_INFINITY;
				} else {
					return Double.NaN;
				}
			}
			return intA / intB;
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a) / castToShort(b);
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) / castToByte(b);
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static boolean gt(Object a, Object b) {
		if (a == null || a == EVAL_VALUE_NULL) {
			return false;
		}

		if (b == null || a == EVAL_VALUE_NULL) {
			return true;
		}

		if (a instanceof String || b instanceof String) {
			return castToString(a).compareTo(castToString(b)) > 0;
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).compareTo(castToDecimal(b)) > 0;
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).compareTo(castToBigInteger(b)) > 0;
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a) > castToLong(b);
		}

		if (a instanceof Integer || b instanceof Integer) {
			return castToInteger(a) > castToInteger(b);
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a) > castToShort(b);
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) > castToByte(b);
		}

		if (a instanceof Date || b instanceof Date) {
			Date d1 = castToDate(a);
			Date d2 = castToDate(b);

			if (d1 == d2) {
				return false;
			}

			if (d1 == null) {
				return false;
			}

			if (d2 == null) {
				return true;
			}

			return d1.compareTo(d2) > 0;
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static boolean gteq(Object a, Object b) {
		if (eq(a, b)) {
			return true;
		}

		return gt(a, b);
	}

	public static boolean lt(Object a, Object b) {
		if (a == null) {
			return true;
		}

		if (b == null) {
			return false;
		}

		if (a instanceof String || b instanceof String) {
			return (castToString(a)).compareTo(castToString(b)) < 0;
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).compareTo(castToDecimal(b)) < 0;
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).compareTo(castToBigInteger(b)) < 0;
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a) < castToLong(b);
		}

		if (a instanceof Integer || b instanceof Integer) {
			Integer intA = castToInteger(a);
			Integer intB = castToInteger(b);
			return intA < intB;
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a) < castToShort(b);
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) < castToByte(b);
		}

		if (a instanceof Date || b instanceof Date) {
			Date d1 = castToDate(a);
			Date d2 = castToDate(b);

			if (d1 == d2) {
				return false;
			}

			if (d1 == null) {
				return true;
			}

			if (d2 == null) {
				return false;
			}

			return d1.compareTo(d2) < 0;
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static boolean lteq(Object a, Object b) {
		if (eq(a, b)) {
			return true;
		}

		return lt(a, b);
	}

	public static boolean eq(Object a, Object b) {
		if (a == b) {
			return true;
		}

		if (a == null || b == null) {
			return false;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return false;
		}

		if (a.equals(b)) {
			return true;
		}

		if (a instanceof String || b instanceof String) {
			return castToString(a).equals(castToString(b));
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).compareTo(castToDecimal(b)) == 0;
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).compareTo(castToBigInteger(b)) == 0;
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a).equals(castToLong(b));
		}

		if (a instanceof Integer || b instanceof Integer) {
			Integer inta = castToInteger(a);
			Integer intb = castToInteger(b);
			if (inta == null || intb == null) {
				return false;
			}
			return inta.equals(intb);
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a).equals(castToShort(b));
		}

		if (a instanceof Boolean || b instanceof Boolean) {
			return castToBoolean(a).equals(castToBoolean(b));
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a).equals(castToByte(b));
		}

		if (a instanceof Date || b instanceof Date) {
			Date d1 = castToDate(a);
			Date d2 = castToDate(b);

			if (d1 == d2) {
				return true;
			}

			if (d1 == null || d2 == null) {
				return false;
			}

			return d1.equals(d2);
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static Object add(Object a, Object b) {
		if (a == null) {
			return b;
		}

		if (b == null) {
			return a;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return EVAL_VALUE_NULL;
		}

		if (a instanceof String && !(b instanceof String)) {
			a = castToNumber((String) a);
		}

		if (b instanceof String && !(a instanceof String)) {
			b = castToNumber((String) b);
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).add(castToDecimal(b));
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).add(castToBigInteger(b));
		}

		if (a instanceof Double || b instanceof Double) {
			return castToDouble(a) + castToDouble(b);
		}

		if (a instanceof Float || b instanceof Float) {
			return castToFloat(a) + castToFloat(b);
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a) + castToLong(b);
		}

		if (a instanceof Integer || b instanceof Integer) {
			return castToInteger(a) + castToInteger(b);
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a) + castToShort(b);
		}

		if (a instanceof Boolean || b instanceof Boolean) {
			int aI = 0, bI = 0;
			if (castToBoolean(a))
				aI = 1;
			if (castToBoolean(b))
				bI = 1;
			return aI + bI;
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) + castToByte(b);
		}

		if (a instanceof String && b instanceof String) {
			return castToString(a) + castToString(b);
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static Object sub(Object a, Object b) {
		if (a == null) {
			return null;
		}

		if (b == null) {
			return a;
		}

		if (a == EVAL_VALUE_NULL || b == EVAL_VALUE_NULL) {
			return EVAL_VALUE_NULL;
		}

		if (a instanceof Date || b instanceof Date) {
			return EvalVisitor.EVAL_ERROR;
		}

		if (a instanceof String) {
			a = castToNumber((String) a);
		}

		if (b instanceof String) {
			b = castToNumber((String) b);
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).subtract(castToDecimal(b));
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).subtract(castToBigInteger(b));
		}

		if (a instanceof Double || b instanceof Double) {
			return castToDouble(a) - castToDouble(b);
		}

		if (a instanceof Float || b instanceof Float) {
			return castToFloat(a) - castToFloat(b);
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a) - castToLong(b);
		}

		if (a instanceof Integer || b instanceof Integer) {
			return castToInteger(a) - castToInteger(b);
		}

		if (a instanceof Short || b instanceof Short) {
			return castToShort(a) - castToShort(b);
		}

		if (a instanceof Boolean || b instanceof Boolean) {
			int aI = 0, bI = 0;
			if (castToBoolean(a))
				aI = 1;
			if (castToBoolean(b))
				bI = 1;
			return aI - bI;
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) - castToByte(b);
		}

		// return SQLEvalVisitor.EVAL_ERROR;
		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static Object multi(Object a, Object b) {
		if (a == null || b == null) {
			return null;
		}

		if (a instanceof String) {
			a = castToNumber((String) a);
		}

		if (b instanceof String) {
			b = castToNumber((String) b);
		}

		if (a instanceof BigDecimal || b instanceof BigDecimal) {
			return castToDecimal(a).multiply(castToDecimal(b));
		}

		if (a instanceof BigInteger || b instanceof BigInteger) {
			return castToBigInteger(a).multiply(castToBigInteger(b));
		}

		if (a instanceof Double || b instanceof Double) {
			return castToDouble(a) * castToDouble(b);
		}

		if (a instanceof Float || b instanceof Float) {
			return castToFloat(a) * castToFloat(b);
		}

		if (a instanceof Long || b instanceof Long) {
			return castToLong(a) * castToLong(b);
		}

		if (a instanceof Integer || b instanceof Integer) {
			return castToInteger(a) * castToInteger(b);
		}

		if (a instanceof Short || b instanceof Short) {
			Short shortA = castToShort(a);
			Short shortB = castToShort(b);

			if (shortA == null || shortB == null) {
				return null;
			}

			return shortA * shortB;
		}

		if (a instanceof Byte || b instanceof Byte) {
			return castToByte(a) * castToByte(b);
		}

		throw new IllegalArgumentException(a.getClass() + " and " + b.getClass() + " not supported.");
	}

	public static boolean like(String input, String pattern) {
		if (pattern == null) {
			throw new IllegalArgumentException("pattern is null");
		}

		StringBuilder regexprBuilder = new StringBuilder(pattern.length() + 4);

		final int STAT_NOTSET = 0;
		final int STAT_RANGE = 1;
		final int STAT_LITERAL = 2;

		int stat = STAT_NOTSET;

		int blockStart = -1;
		for (int i = 0; i < pattern.length(); ++i) {
			char ch = pattern.charAt(i);

			if (stat == STAT_LITERAL //
					&& (ch == '%' || ch == '_' || ch == '[')) {
				String block = pattern.substring(blockStart, i);
				regexprBuilder.append("\\Q");
				regexprBuilder.append(block);
				regexprBuilder.append("\\E");
				blockStart = -1;
				stat = STAT_NOTSET;
			}

			if (ch == '%') {
				regexprBuilder.append(".*");
			} else if (ch == '_') {
				regexprBuilder.append('.');
			} else if (ch == '[') {
				if (stat == STAT_RANGE) {
					throw new IllegalArgumentException("illegal pattern : " + pattern);
				}
				stat = STAT_RANGE;
				blockStart = i;
			} else if (ch == ']') {
				if (stat != STAT_RANGE) {
					throw new IllegalArgumentException("illegal pattern : " + pattern);
				}
				String block = pattern.substring(blockStart, i + 1);
				regexprBuilder.append(block);

				blockStart = -1;
			} else {
				if (stat == STAT_NOTSET) {
					stat = STAT_LITERAL;
					blockStart = i;
				}

				if (stat == STAT_LITERAL && i == pattern.length() - 1) {
					String block = pattern.substring(blockStart, i + 1);
					regexprBuilder.append("\\Q");
					regexprBuilder.append(block);
					regexprBuilder.append("\\E");
				}
			}
		}
		if ("%".equals(pattern) || "%%".equals(pattern)) {
			return true;
		}

		String regexpr = regexprBuilder.toString();
		return Pattern.matches(regexpr, input);
	}

	public static boolean visit(EvalVisitor visitor, Identifier x) {
		x.putAttribute(EVAL_EXPR, x);
		return false;
	}
}
